package com.opages.netty.demo.lesson.dark_horse3;

import com.opages.netty.demo.lesson.ByteBufferUtil;
import io.netty.bootstrap.Bootstrap;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.buffer.ByteBufUtil;
import io.netty.channel.*;
import io.netty.channel.embedded.EmbeddedChannel;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.util.CharsetUtil;
import io.netty.util.concurrent.DefaultPromise;
import io.netty.util.concurrent.Promise;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;

import java.io.IOException;
import java.nio.charset.Charset;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

/**
 * @author daizhong.liu
 * @create 2022-01-04 9:52
 **/
@Slf4j
public class NettyTest {
    @Test
    public void eventLoopTest() throws IOException {
        EventLoopGroup elg = new NioEventLoopGroup(2);
        elg.next().execute(()->{
            log.debug("执行线程");
        });
        elg.next().scheduleAtFixedRate(()->{
            log.debug("执行定时任务...");
        },0,1, TimeUnit.SECONDS);
//        elg.shutdownGracefully();
        System.in.read();
    }
    @Test
    public void simpleServer() throws IOException {
        ChannelFuture future = new ServerBootstrap()
                .group(new NioEventLoopGroup())
                .channel(NioServerSocketChannel.class)
                .childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel ch) throws Exception {
                        ch.pipeline().addLast(new ChannelInboundHandlerAdapter(){
                            @Override
                            public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                                ByteBuf buf = (ByteBuf) msg;
                                log.debug("{}:msg={}",Thread.currentThread().getName(),buf.toString());
                            }
                        });
                    }
                }).bind(8889);
        future.addListener((listen)->{
            if(listen.isSuccess()) {
                log.debug("服务端启动成功...");
            }
        });
        System.in.read();
    }
    @Test
    public void simpleClient() throws InterruptedException {
        Channel channel = new Bootstrap()
                .group(new NioEventLoopGroup())
                .channel(NioSocketChannel.class)
                .handler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel ch) throws Exception {
                        ch.pipeline().addLast(new StringEncoder()).addLast(new StringDecoder());
                    }
                }).connect("127.0.0.1", 8889)
                .sync()
                .channel();
        log.debug(channel.toString());
    }

    @Test
    public void promiseTest() throws ExecutionException, InterruptedException {
        EventLoopGroup group = new NioEventLoopGroup(2);
        EventLoop eventLoop = group.next();
        Promise<String> promise = new DefaultPromise(eventLoop);

        new Thread(()->{
            try {
                log.debug("异常线程准备睡眠:{}",Thread.currentThread().getName());
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            promise.setSuccess("三秒后的成功结果");
        }).start();

        log.debug("主线程{},promise的结果值:{}",Thread.currentThread().getName(),promise.get());
    }

    @Test
    public void pipelineTest() throws IOException {
        ChannelFuture channelFuture = new ServerBootstrap()
                .group(new NioEventLoopGroup(1), new NioEventLoopGroup(2))
                .channel(NioServerSocketChannel.class)
                .childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel ch) throws Exception {
                        ch.pipeline().addLast("inboundHandler1",new ChannelInboundHandlerAdapter(){
                            @Override
                            public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                                log.debug("\n{}:{}\n",Thread.currentThread().getName(),"InboundHandler1111");
                                super.channelRead(ctx, msg);
                            }
                        })
                        .addLast("inboundHandler2",new ChannelInboundHandlerAdapter(){
                            @Override
                            public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                                log.debug("\n{}:{}\n",Thread.currentThread().getName(),"InboundHandler2222");
                                ch.writeAndFlush("server...");
                                super.channelRead(ctx, msg);
                            }
                        })
                        .addLast("outboundHandler3",new ChannelOutboundHandlerAdapter(){
                            @Override
                            public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
                                log.debug("\n{}:{}\n",Thread.currentThread().getName(),"OutboundHandler3333");
                                super.write(ctx, msg, promise);
                            }
                        })
                        .addLast("outboundHandler4",new ChannelOutboundHandlerAdapter(){
                            @Override
                            public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
                                log.debug("\n{}:{}\n",Thread.currentThread().getName(),"OutboundHandler4444");
                                super.write(ctx, msg, promise);
                            }
                        });
                    }
                })
                .bind(8889);
        System.in.read();
    }
    @Test
    public void embeddedTest() {
        ChannelInboundHandlerAdapter c1 = new ChannelInboundHandlerAdapter() {
            @Override
            public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                log.debug("\n{}:{}\n",Thread.currentThread().getName(),"InboundHandler1111");
                super.channelRead(ctx, msg);
            }
        };
        ChannelInboundHandlerAdapter c2 = new ChannelInboundHandlerAdapter() {
            @Override
            public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                log.debug("\n{}:{}\n",Thread.currentThread().getName(),"InboundHandler2222");
                super.channelRead(ctx, msg);
            }
        };
        ChannelOutboundHandlerAdapter c3 = new ChannelOutboundHandlerAdapter() {
            @Override
            public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
                log.debug("\n{}:{}\n",Thread.currentThread().getName(),"OutboundHandler3333");
                super.write(ctx, msg, promise);
            }
        };
        ChannelOutboundHandlerAdapter c4 = new ChannelOutboundHandlerAdapter() {
            @Override
            public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
                log.debug("\n{}:{}\n",Thread.currentThread().getName(),"OutboundHandler4444");
                super.write(ctx, msg, promise);
            }
        };

        EmbeddedChannel embeddedChannel = new EmbeddedChannel(c1,c2,c3,c4);
        embeddedChannel.writeInbound("abc");
        System.err.println("\n\n\n---------------------------------\n\n\n");
        embeddedChannel.writeOutbound("ccc");
    }
    @Test
    public void bytebufTest() {
//        ByteBuf buf = ByteBufAllocator.DEFAULT.buffer(16);
//        buf.writeBytes("hello world,this is netty bytebuf oh oh".getBytes(CharsetUtil.UTF_8));
//        ByteBufferUtil.log(buf);


//        ByteBuf buffer = ByteBufAllocator.DEFAULT.buffer(16);
//        System.out.println(buffer.getClass());
//        buffer = ByteBufAllocator.DEFAULT.heapBuffer(16);
//        System.out.println(buffer.getClass());
//        buffer = ByteBufAllocator.DEFAULT.directBuffer(16);
//        System.out.println(buffer.getClass());


        // 创建ByteBuf
        ByteBuf buffer = ByteBufAllocator.DEFAULT.buffer(16, 20);
        ByteBufferUtil.log(buffer);

        // 向buffer中写入数据
        buffer.writeBytes(new byte[]{1, 2, 3, 4});
        ByteBufferUtil.log(buffer);

        buffer.writeInt(5);
        ByteBufferUtil.log(buffer);

        buffer.writeIntLE(6);
        ByteBufferUtil.log(buffer);

        buffer.writeLong(7);
        ByteBufferUtil.log(buffer);
    }
    @Test
    public void sliceTest() {
        ByteBuf buf = ByteBufAllocator.DEFAULT.buffer(16,32);
//        buf.writeBytes(new byte[]{12,13,14,15,16});
        buf.writeBytes("hello,world".getBytes(CharsetUtil.UTF_8));
        ByteBuf slice1 = buf.slice(0, 5);
        ByteBuf slice2 = buf.slice(6, 10);
        ByteBufferUtil.log(slice1);
        System.err.println("###################");
        ByteBufferUtil.log(slice2);
    }
    /**
     *
     public LengthFieldBasedFrameDecoder(
        int maxFrameLength,int lengthFieldOffset,
        int lengthFieldLength,int lengthAdjustment,
        int initialBytesToStrip)

     maxFrameLength 数据最大长度
        表示数据的最大长度（包括附加信息、长度标识等内容）
     lengthFieldOffset 数据长度标识的起始偏移量
        用于指明数据第几个字节开始是用于标识有用字节长度的，因为前面可能还有其他附加信息
     lengthFieldLength 数据长度标识所占字节数（用于指明有用数据的长度）
        数据中用于表示有用数据长度的标识所占的字节数
     lengthAdjustment 长度表示与有用数据的偏移量
        用于指明数据长度标识和有用数据之间的距离，因为两者之间还可能有附加信息
     initialBytesToStrip 数据读取起点
        读取起点，不读取 0 ~ initialBytesToStrip 之间的数据

     lengthFieldOffset   = 1 (= the length of HDR1)
     lengthFieldLength   = 2
     lengthAdjustment    = 1 (= the length of HDR2)
     initialBytesToStrip = 3 (= the length of HDR1 + LEN)

     BEFORE DECODE (16 bytes)                       AFTER DECODE (13 bytes)
     +------+--------+------+----------------+      +------+----------------+
     | HDR1 | Length | HDR2 | Actual Content |----->| HDR2 | Actual Content |
     | 0xCA | 0x000C | 0xFE | "HELLO, WORLD" |      | 0xFE | "HELLO, WORLD" |
     +------+--------+------+----------------+      +------+----------------+
     长度标识前面有1个字节的其他内容，后面也有1个字节的其他内容，读取时从长度标识之后3个字节处开始读取，
     即读取 0xFE HELLO, WORLD
     **/
    @Test
    public void encoderTest() {
        EmbeddedChannel embeddedChannel = new EmbeddedChannel(
                new LengthFieldBasedFrameDecoder(1024,1,4,1,0),
                new LoggingHandler(LogLevel.DEBUG)
        );
        //模拟客户端写入
        ByteBuf buf = ByteBufAllocator.DEFAULT.buffer();
        send(buf,"hello");
        embeddedChannel.writeInbound(buf);
        send(buf,"netty");
        embeddedChannel.writeInbound(buf);
    }

    private void send(ByteBuf buf,String msg) {
        int len = msg.length();
        byte[] content = msg.getBytes(CharsetUtil.UTF_8);
        // 写入长度标识前的其他信息，1个字节
        buf.writeByte(0xCA);
        // 写入数据长度标识,int类型4个字节
        buf.writeInt(len);
        // 写入长度标识后的其他信息，1个字节
        buf.writeByte(0xFE);
        // 写入具体的数据
        buf.writeBytes(content);

    }
}
